/*
 * PIPEFMT format
 * Copyright (c) 2012 Juergen Bohl
 *
 * This file is part of FFmpeg.
 *
 * FFmpeg is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * FFmpeg is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with FFmpeg; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
 */

#include "avformat.h"
#include "internal.h"
#include "libavcodec/avcodec.h"
#include "libavutil/pixdesc.h"

static const char PIPEFMT_MAGIC[] = "PIPEFORMAT\n";
#define PIPEFMT_MAGIC_SIZE (sizeof(PIPEFMT_MAGIC)-1)

static const char VSTREAM_IDENTIFIER[] = "VSTREAM:";
#define VSTREAM_IDENTIFIER_SIZE  (sizeof(VSTREAM_IDENTIFIER)-1)
static const char ASTREAM_IDENTIFIER[] = "ASTREAM:";
#define ASTREAM_IDENTIFIER_SIZE  (sizeof(VSTREAM_IDENTIFIER)-1)
static const char END_IDENTIFIER[] = "END:";
#define END_IDENTIFIER_SIZE  (sizeof(END_IDENTIFIER)-1)

#define MAX_VSTREAMS 2
#define MAX_ASTREAMS 8

/*
Description of the format:

At offset 0 - 10:       Magic "PIPEFORMAT\n"  (0x50 0x49 0x50 0x45 0x46 0x4F 0x52 0x4D 0x41 0x54 0x0A)

Then follows the header defining the data format. For each stream (either video or audio stream) we have a line
starting either with "ASTREAM:" or "VSTREAM:". Each line then contains parameters that specify the format of
the stream, in the form of key-value-pairs KEY=VALUE. The key-value-pairs are separated by spaces.
The order of the lines specifies the order of the data packets. The
lines are terminated with a newline ('\n'=0x0a).
The following values can be specified:
VSTREAM: 
W     width of the images                         mandatory
H     height of the images                        mandatory
F     framerate in frames per seconds, specified
      as a fraction (e.g. 25:1)                   
A     pixel aspect ratio

ASTREAM:
R     sampling rate (samples per second)          mandatory
C     number of channels                          mandatory
F     format ("PCM_S16LE" or "PCM_F32LE")         mandatory

The list of "VSTREAM:" or "ASTREAM:" specifications is terminated with a line "END:\n".

Then follows the video-/audio-data, one packet containing one frame (or audio for one frame). The order 
must be same as specified in the header. A video-packet starts with "VSTREAM[n]:\n", where [n] is the 
number of the stream (audio-/video- are counted independently). An audio-packet is started with
"ASTREAM[n]:". Then it must be followed by (again key-value syntax) a string "C=xxx" (and a newline),
where 'xxx' is the number of samples PER channel in this packet.
*/

static int pipefmt_chkmagic(AVIOContext* pContext)
{
    unsigned char buf[PIPEFMT_MAGIC_SIZE];
    int i = avio_read(pContext,buf,sizeof(buf));
    if (i<0||i!=PIPEFMT_MAGIC_SIZE)
        return -1;
    return memcmp(buf,PIPEFMT_MAGIC,PIPEFMT_MAGIC_SIZE)==0 ? 1 : -1;
}

enum LINETYPE
{
    LINETYPE_INVALID,
    LINETYPE_VSTREAM,
    LINETYPE_ASTREAM,
    LINETYPE_END
};

static enum LINETYPE pipefmt_determineLinetype(AVIOContext* pContext)
{
    unsigned char buf[8];  // must have the size max(VSTREAM_IDENTIFIER_SIZE,ASTREAM_IDENTIFIER_SIZE,END_IDENTIFIER_SIZE)
    memset(buf,0,sizeof(buf));
    for (int i=0; i<sizeof(buf); i++)
    {
        unsigned char c = avio_r8(pContext);
        buf[i] = c;
        if (c==':'||c=='\n')
            break;
    }

    if (memcmp(buf,VSTREAM_IDENTIFIER,VSTREAM_IDENTIFIER_SIZE)==0)
        return LINETYPE_VSTREAM;
    if (memcmp(buf,ASTREAM_IDENTIFIER,ASTREAM_IDENTIFIER_SIZE)==0)
        return LINETYPE_ASTREAM;
    if (memcmp(buf,END_IDENTIFIER,END_IDENTIFIER_SIZE)==0)
        return LINETYPE_END;
    return LINETYPE_INVALID;
}

static int pipefmt_readLine(AVIOContext* pContext,char* pBuf,int sizeBuf)
{
    // read at most sizeBuf bytes from the stream, terminate if we find a '\n'
    for (int n=0;n<sizeBuf;n++)
    {
        *(pBuf+n) = avio_r8(pContext);
        if (*(pBuf+n)=='\n')
        {
            *(pBuf+n)='\0';
            return 1;
        }
    }

    return -1;
}

static int pipefmt_skipSpacesUntilLf(AVIOContext* pContext)
{
    for (;;)
    {
        unsigned char c = avio_r8(pContext);
        if (c=='\n')
            return 1;
        if (c!=' ')
            return -1;
    }
}

static int pipefmt_getValueKey(const char* sz,char* key,int sizeKey,char* value,int sizeValue)
{
    const char* szStart = sz;
    for (;;sz++)
    {
        if (*sz=='\0')
            return -1;
        if (*sz!=' ')
            break;
    }

    int i;
    // read until "="
    for (i=0;;sz++)
    {
        if (*sz=='\0')
            return -1;
        if (*sz=='=')
        {
            key[i] = '\0';
            sz++;
            break;
        }

        key[i++] = *sz;
        if (i>=sizeKey)
            return -1;
    }

    // read until the next space " " (or terminator)
    for (i=0;;sz++)
    {
        if (*sz=='\0')
        {
            value[i] = '\0';
            return i>0?(sz-szStart):-1;
        }

        if (*sz==' ')
        {
            value[i] = '\0';
            return i>0?(sz-szStart):-1;
        }

        value[i++] = *sz;
        if (i>=sizeValue)
            return -1;
    }
}

typedef struct {
    int     width;
    int     height;
    int     rateNumerator;
    int     rateDenominator;
    int     pixelAspectNumerator;
    int     pixelAspectDenominator;
} VStreamInfo;

typedef struct {
    enum CodecID audioCodecId;
    int     numberOfChannels;
    int     sampleRate;
} AStreamInfo;

struct PipeFormatContext {
    __int64     readPacketCount;    // counts the number of call to read_packet
    int         vStreamCount;
    int         aStreamCount;
    VStreamInfo vStreams[MAX_VSTREAMS];
    AStreamInfo aStreams[MAX_ASTREAMS];
};

static void pipefmt_clearVStreamInfo(VStreamInfo* pVStreamInfo)
{
    pVStreamInfo->width = pVStreamInfo->height = -1;
    pVStreamInfo->rateNumerator = pVStreamInfo->rateDenominator = 0;
    pVStreamInfo->pixelAspectNumerator = pVStreamInfo->pixelAspectDenominator = 0;
}

static void pipefmt_clearAStreamInfo(AStreamInfo* pAStreamInfo)
{
    pAStreamInfo->audioCodecId = CODEC_ID_NONE;
    pAStreamInfo->numberOfChannels = -1;
    pAStreamInfo->sampleRate = -1;
}

static int pipefmt_readVStream(AVIOContext* pContext,VStreamInfo* pVStreamInfo)
{
    pipefmt_clearVStreamInfo(pVStreamInfo);

    char buf[160];
    int i = pipefmt_readLine(pContext,buf,sizeof(buf));
    if (i<0)
        return i;

    char key[20];char value[100];
    const char* pBuf = buf;
    for (;;)
    {
        i = pipefmt_getValueKey(pBuf,key,sizeof(key),value,sizeof(value));
        if (i<0)
            break;
        pBuf += i;
        if (strcmp(key,"W")==0)
        {
            if (sscanf(value,"%i",&pVStreamInfo->width)!=1)
                return -1;
        }
        else if (strcmp(key,"H")==0)
        {
            if (sscanf(value,"%i",&pVStreamInfo->height)!=1)
                return -1;
        }
        else if (strcmp(key,"F")==0)
        {
            // the framerate (in frames per seconds), specified as a fraction
            if (sscanf(value,"%i:%i",&pVStreamInfo->rateNumerator,&pVStreamInfo->rateDenominator)!=2)
                return -1;
        }
        else if (strcmp(key,"A")==0)
        {
            if (sscanf(value,"%i:%i",&pVStreamInfo->pixelAspectNumerator,&pVStreamInfo->pixelAspectDenominator)!=2)
                return -1;
        }
    }

    // check validity of VStreamInfo
    if (pVStreamInfo->width<=0               ||
        pVStreamInfo->height<=0              ||
        pVStreamInfo->rateNumerator<0        ||
        pVStreamInfo->rateDenominator<0      ||
        pVStreamInfo->pixelAspectNumerator<0 ||
        pVStreamInfo->pixelAspectDenominator<0)
        return -1;

    return 1;
}

static int pipefmt_getAudioCodecId(const char* szCodecId,enum CodecID* pAudioCodecId)
{
    static struct  
    {
        const char*     codec;
        enum CodecID    id;
    } CodecIDs[] =
    {
        { "PCM_S16LE", CODEC_ID_PCM_S16LE},
        { "PCM_F32LE", CODEC_ID_PCM_F32LE}
    };

    for (int i=0;i<FF_ARRAY_ELEMS(CodecIDs);i++)
    {
        if (strcmp(CodecIDs[i].codec,szCodecId)==0)
        {
            *pAudioCodecId = CodecIDs[i].id;
            return 1;
        }
    }

    return -1;
}

static int pipefmt_readAStream(AVIOContext* pContext,AStreamInfo* pAStreamInfo)
{
    pipefmt_clearAStreamInfo(pAStreamInfo);

    char buf[160];
    int i = pipefmt_readLine(pContext,buf,sizeof(buf));
    if (i<0)
        return i;

    char key[20];char value[100];
    const char* pBuf = buf;
    for (;;)
    {
        i = pipefmt_getValueKey(pBuf,key,sizeof(key),value,sizeof(value));
        if (i<0)
            break;

        pBuf += i;
        if (strcmp(key,"C")==0)
        {
            if (sscanf(value,"%i",&pAStreamInfo->numberOfChannels)!=1)
                return -1;
        }
        else if (strcmp(key,"R")==0)
        {
            if (sscanf(value,"%i",&pAStreamInfo->sampleRate)!=1)
                return -1;
        }
        else if (strcmp(key,"F")==0)
        {
            if (pipefmt_getAudioCodecId(value,&pAStreamInfo->audioCodecId)<0)
                return -1;
        }
    }

    if (pAStreamInfo->audioCodecId==CODEC_ID_NONE ||
        pAStreamInfo->sampleRate<=0               ||
        pAStreamInfo->numberOfChannels<=0)
        return -1;

    return 1;
}

/*
format:
VSTREAM: W=1230 H=3939   \n
ASTREAM: R=44100         \n
END:
*/
static int pipefmt_read_header(AVFormatContext *s)
{
    // first, check the magic
    if (pipefmt_chkmagic(s->pb)<0)
        return -1;
    struct PipeFormatContext* pPipeFmtCntx = s->priv_data;
    pPipeFmtCntx->readPacketCount = pPipeFmtCntx->vStreamCount = pPipeFmtCntx->aStreamCount = 0;

    for (;;)
    {
        // read the stream-specifier (VSTREAM:, ASTREAM: or END:)
        enum LINETYPE lt = pipefmt_determineLinetype(s->pb);
        if (lt == LINETYPE_END)
        {
            // we require a terminating '\n' (so we have to skip spaces until we find a '\n')
            if (pipefmt_skipSpacesUntilLf(s->pb)<0)
                return -1;
            break;
        }
        else
            switch (lt)
            {
            case LINETYPE_INVALID:
                return -1;
            case LINETYPE_VSTREAM:
                if (pPipeFmtCntx->vStreamCount>=FF_ARRAY_ELEMS(pPipeFmtCntx->vStreams))
                    return -1;
                if (pipefmt_readVStream(s->pb,&pPipeFmtCntx->vStreams[(pPipeFmtCntx->vStreamCount)++])<0)
                    return -1;
                break;
            case LINETYPE_ASTREAM:
                if (pPipeFmtCntx->aStreamCount>=FF_ARRAY_ELEMS(pPipeFmtCntx->aStreams))
                    return -1;
                if (pipefmt_readAStream(s->pb,&pPipeFmtCntx->aStreams[(pPipeFmtCntx->aStreamCount)++])<0)
                    return -1;
                break;
            }
    }

    if (pPipeFmtCntx->aStreamCount==0 && pPipeFmtCntx->vStreamCount==0)
        return -1;

    for (int i=0;i<pPipeFmtCntx->vStreamCount;i++)
    {
        AVStream* pStream = avformat_new_stream(s,NULL);
        if (!pStream)
            return AVERROR(ENOMEM);
        VStreamInfo* pVStreamInfo = &pPipeFmtCntx->vStreams[i];
        pStream->codec->pix_fmt                = PIX_FMT_YUV420P;
        pStream->codec->width  = pVStreamInfo->width;
        pStream->codec->height = pVStreamInfo->height;
        pStream->codec->codec_type             = AVMEDIA_TYPE_VIDEO;
        pStream->codec->codec_id               = CODEC_ID_RAWVIDEO;
        pStream->sample_aspect_ratio           = (AVRational){ pVStreamInfo->pixelAspectNumerator, pVStreamInfo->pixelAspectDenominator};
        av_reduce(&pVStreamInfo->rateNumerator, &pVStreamInfo->rateDenominator, pVStreamInfo->rateNumerator, pVStreamInfo->rateDenominator, (1UL << 31) - 1);
        avpriv_set_pts_info(pStream, 64, pVStreamInfo->rateDenominator, pVStreamInfo->rateNumerator);
    }

    for (int i=0;i<pPipeFmtCntx->aStreamCount;i++)
    {
        AVStream* pStream = avformat_new_stream(s,NULL);
        if (!pStream)
            return AVERROR(ENOMEM);
        avpriv_set_pts_info(pStream, 64, 1, 30000);
        pStream->codec->codec_type = AVMEDIA_TYPE_AUDIO;
        pStream->codec->codec_id   = CODEC_ID_PCM_S16LE;
        pStream->codec->sample_rate = 48000;
        pStream->codec->channels    = 2;
        pStream->codec->bit_rate    = pStream->codec->channels * pStream->codec->sample_rate * 16;
        pStream->start_time = 0;
    }

    return 0;
}

static int pipefmt_readPastPacketStreamHeader(AVIOContext* pAvioContext,int streamNo)
{
    char szMagicHdr[30];
    snprintf(szMagicHdr,sizeof(szMagicHdr),"VSTREAM#%i:",streamNo);
    size_t len = strlen(szMagicHdr);
    for (int i=0;i<len;i++)
    {
        if (szMagicHdr[i] != avio_r8(pAvioContext))
            return -1;
    }

    // now skip white spaces until we find a '\n' (may add further parameters in the future...)
    for (;;)
    {
        char c = avio_r8(pAvioContext);
        if (c == '\n')
            break;
        if (c != ' ')
            return -1;
    }

    return 1;
}

static int pipefmt_readVPacket(AVIOContext* pAvioContext,AVStream* pAvStream,const VStreamInfo* pVStreamInfo,int videoStreamNo,int streamIndex,AVPacket* pkt)
{
    int r = pipefmt_readPastPacketStreamHeader(pAvioContext,videoStreamNo);
    if (r<0)
        return r;

    // ok, what now follows is the raw data for the frame
    int packetSize = avpicture_get_size(pAvStream->codec->pix_fmt,pVStreamInfo->width,pVStreamInfo->height);
    if (packetSize < 0)
        return -1;
    if (av_get_packet(pAvioContext,pkt,packetSize) != packetSize)
        return AVERROR(EIO);
    pkt->stream_index = streamIndex;
    return 1;
}

static int pipefmt_readPastPacketStreamMagic(AVIOContext* pAvioContext,int streamNo)
{
    char szMagicHdr[30];
    snprintf(szMagicHdr,sizeof(szMagicHdr),"ASTREAM#%i:",streamNo);
    size_t len = strlen(szMagicHdr);
    for (int i=0;i<len;i++)
    {
        if (szMagicHdr[i] != avio_r8(pAvioContext))
            return -1;
    }

    return 1;
}

// ASTREAM#[0-9]: C=1920\n
static int pipefmt_readAPacketStreamHeader(AVIOContext* pAvioContext,int streamNo,int* pSamplesCnt)
{
    int r = pipefmt_readPastPacketStreamMagic(pAvioContext,streamNo);
    if (r<0)
        return r;
    char buf[160];
    int i = pipefmt_readLine(pAvioContext,buf,sizeof(buf));
    if (i<0)
        return i;

    *pSamplesCnt = -1;
    char key[20];char value[100];
    const char* pBuf = buf;
    for (;;)
    {
        i = pipefmt_getValueKey(pBuf,key,sizeof(key),value,sizeof(value));
        if (i<0)
            break;
        pBuf += i;
        if (strcmp(key,"C")==0)
        {
            if (sscanf(value,"%i",pSamplesCnt)!=1)
                return -1;
        }
    }

    if (*pSamplesCnt<0)
        return -1;

    return 1;
}

static int pipefmt_getAudioPacketSize(const AStreamInfo* pAStreamInfo, int sampleCount)
{
    switch (pAStreamInfo->audioCodecId)
    {
    case CODEC_ID_PCM_S16LE:
        return pAStreamInfo->numberOfChannels * sampleCount * sizeof(short);
    }

    return -1;
}

static int pipefmt_readAPacket(AVIOContext* pAvioContext,AVStream* pAvStream,const AStreamInfo* pAStreamInfo,int audioStreamNo,int streamIndex,AVPacket* pkt)
{
    int sampleCnt;
    int r = pipefmt_readAPacketStreamHeader(pAvioContext,audioStreamNo,&sampleCnt);
    if (r<0)
        return r;
    int packetSize = pipefmt_getAudioPacketSize(pAStreamInfo,sampleCnt);
    if (packetSize<0)
        return AVERROR(EBADF);
    if (av_get_packet(pAvioContext,pkt,packetSize) != packetSize)
        return AVERROR(EIO);
    pkt->stream_index = streamIndex;
    return 1;
}

static int pipefmt_read_packet(AVFormatContext* s, AVPacket *pkt)
{
    struct PipeFormatContext* pPipeFmtCntx = s->priv_data;
    int numberOfStreams = pPipeFmtCntx->vStreamCount + pPipeFmtCntx->aStreamCount;
    int rem = pPipeFmtCntx->readPacketCount % numberOfStreams;

    // we expect first the video streams (in exactly the order 0, 1,2 ...) and then the
    // audio streams, again exactly in order
    int r;
    if (rem<pPipeFmtCntx->vStreamCount)
    {
        r = pipefmt_readVPacket(s->pb,s->streams[rem],&pPipeFmtCntx->vStreams[rem],rem,rem,pkt);
    }
    else
    {
        int aIndex = rem - pPipeFmtCntx->vStreamCount;
        r = pipefmt_readAPacket(s->pb,s->streams[rem],&pPipeFmtCntx->aStreams[aIndex],aIndex,rem,pkt);
    }

    pPipeFmtCntx->readPacketCount++;
    return r;
}

static int pipefmt_probe(AVProbeData *pd)
{
    if (pd->buf_size >= PIPEFMT_MAGIC_SIZE && memcmp(pd->buf,PIPEFMT_MAGIC,PIPEFMT_MAGIC_SIZE) == 0)
    {
        return AVPROBE_SCORE_MAX;
    }
    else
        return 0;
}

#if CONFIG_PIPEFMT_DEMUXER
AVInputFormat ff_pipefmt_demuxer = {
    .name           = "pipefmt",
    .long_name      = NULL_IF_CONFIG_SMALL("PIPEFMT pipe format"),
    .priv_data_size = sizeof(struct PipeFormatContext),
    .read_probe     = pipefmt_probe,
    .read_header    = pipefmt_read_header,
    .read_packet    = pipefmt_read_packet,
    .extensions     = "pipefmt",
};
#endif
